package com.piece.core.web.util;

import com.piece.core.framework.constant.*;
import com.piece.core.framework.enums.StatusType;
import com.piece.core.framework.support.dto.UserDetailDTO;
import com.piece.core.framework.util.basic.ConfigUtil;
import com.piece.core.framework.util.basic.I18nUtil;
import com.piece.core.framework.util.basic.SpringUtil;
import com.piece.core.framework.util.net.URLUtil;
import com.piece.core.framework.util.object.ObjectUtil;
import com.piece.core.framework.util.security.coder.AESCoder;
import com.piece.core.framework.util.security.coder.Base64Coder;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.log.client.LogFactory;
import com.piece.core.log.client.dto.LoginLogDTO;
import com.piece.core.log.config.RequestContextHolder;
import com.piece.core.log.enums.LoginLogType;
import com.piece.core.log.support.LogUtil;
import com.piece.core.web.authority.AuthorityFactory;
import com.piece.core.web.cookie.CookieUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

public class AuthenticationUtil {

    private String applicationName;
    private HttpServletRequest request;
    private LogFactory logFactory;
    private AuthorityFactory authorityFactory;
    private TokenStore tokenStore;

    private static AuthenticationUtil instance;
    private static ReentrantLock lock = new ReentrantLock();

    public static AuthenticationUtil getInstance(HttpServletRequest request) {
        if (null == instance) {
            lock.lock();
            if (null == instance) {
                instance = new AuthenticationUtil();
                instance.applicationName = ConfigUtil.getInstance().get("spring.application.name");
                instance.logFactory = SpringUtil.getBean(LogFactory.class);
                instance.authorityFactory = SpringUtil.getBean(AuthorityFactory.class);
                instance.tokenStore = SpringUtil.getBean(TokenStore.class);
            }
            lock.unlock();
        }
        instance.request = request;
        return instance;
    }

    public void setTokenStore(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    /**
     * 解析token
     */
    public String extractToken() {
        String token = extractHeaderToken();
        if (null == token) {
            token = request.getParameter(ParamConstants.TOKEN);
        }
        if (null == token) {
            token = CookieUtil.getCookieValue(request, CookieConstants.SESSION_KEY);
        }
        return token;
    }

    /**
     * 从Header解析token
     */
    public String extractHeaderToken() {
        Enumeration<String> headers = request.getHeaders(HeaderConstants.Authorization);
        String value;
        while (headers.hasMoreElements()) {
            try {
                value = headers.nextElement();
            } catch (Exception e) {
                return null;
            }
            if (null != value && value.startsWith(FrameWorkConstants.BEARER_TYPE)) {
                String authHeaderValue = value.substring(FrameWorkConstants.BEARER_TYPE.length()).trim();
                int commaIndex = authHeaderValue.indexOf(',');
                if (commaIndex > 0) {
                    authHeaderValue = authHeaderValue.substring(0, commaIndex);
                }
                return authHeaderValue;
            }
        }
        return null;
    }

    /**
     * 解析登录名
     */
    public String extractLoginName() {
        String[] detail = extractLoginInfo();
        return null == detail ? null : detail[0];
    }

    /**
     * 解析登录名和登录密码
     */
    public String[] extractLoginInfo() {
        String detail = request.getParameter(ParamConstants.AUTHENTICATION_INFO);
        try {
            detail = AESCoder.aesDecrypt(detail.trim());
        } catch (Exception e) {
            detail = "";
        }
        return StringUtil.split(detail, FrameWorkConstants.SPLIT_PARAM);
    }

    /**
     * 解析Client
     */
    public String extractClientId() {
        return extractClient()[0];
    }

    /**
     * 解析clientId:clientSecret
     */
    public String[] extractClient() {
        String header = request.getHeader(HeaderConstants.TENANT);
        if (null == header) {
            header = request.getHeader(HeaderConstants.Authorization);
        }
        if (null == header) {
            return extractParamClient();
        }
        return extractHeaderClient(header);
    }

    /**
     * 从Header解析clientId:clientSecret
     */
    public String[] extractHeaderClient(String header) {
        if ((header.startsWith(FrameWorkConstants.BASIC_TYPE))) {
            header = header.substring(FrameWorkConstants.BASIC_TYPE.length()).trim();
        }
        String clientStr = Base64Coder.decode(header);
        String[] clientArr = clientStr.split(":");
        if (clientArr.length != 2) {
            throw new RuntimeException(I18nUtil.message(ExceptionAuthConstants.CLIENT_INVALID));
        }
        return clientArr;
    }

    /**
     * 从参数中解析clientId:clientSecret
     */
    public String[] extractParamClient() {
        String clientId = request.getParameter(ParamConstants.CLIENT_ID);
        String clientSecret = request.getParameter(ParamConstants.CLIENT_SECRET);
        if (null == clientId) {
            throw new UnapprovedClientAuthenticationException(I18nUtil.message(ExceptionAuthConstants.CLIENT_INVALID));
        }
        return new String[]{clientId, clientSecret};
    }

    /**
     * 获取当前Authentication
     */
    public Authentication getAuthentication() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (null != authentication) {
            if (authentication instanceof OAuth2Authentication) {
                return ((OAuth2Authentication) authentication).getUserAuthentication();
            }
            return authentication;
        }
        return getAuthentication(authorityFactory.get().extractToken(request));
    }

    /**
     * 根据accessToken获取Authentication
     */
    public Authentication getAuthentication(String accessToken) {
        OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
        return (null == oAuth2Authentication) ? null : oAuth2Authentication.getUserAuthentication();
    }

    /**
     * 获取登录用户
     */
    public UserDetailDTO getLoginUser() {
        UserDetailDTO user = null;
        Authentication authentication = getAuthentication();
        if (null != authentication) {
            Object details = authentication.getDetails();
            if (details instanceof UserDetailDTO) {
                user = ((UserDetailDTO) details);
            }
        }
        return user;
    }

    /**
     * 获取token用户
     */
    public UserDetailDTO getUserDetail(String accessToken) {
        UserDetailDTO user = null;
        Authentication authentication = getAuthentication(accessToken);
        if (null != authentication) {
            Object details = authentication.getDetails();
            if (details instanceof UserDetailDTO) {
                user = ((UserDetailDTO) details);
            }
        }
        return user;
    }

    public void loginSuccessLog(String username, Map message) {
        try {
            String[] clientInfo = authorityFactory.get().extractClient(request);
            final Map param = (Map) ObjectUtil.clone(message);
            new Thread(() -> {
                RequestContextHolder.set(request, request.getHeader("user-agent"));
                try {
                    LoginLogDTO logDTO = LogUtil.buildLoginLog(applicationName);
                    logDTO.setType(LoginLogType.LOGIN.getType().toString());
                    logDTO.setClientId(null == clientInfo ? null : clientInfo[0]);
                    logDTO.setUserName(username);
                    logDTO.setDesc(URLUtil.appendQueryFromRequest(param));
                    logFactory.getLoginLog().login(logDTO);
                } catch (Exception e) {}
            }).start();
        } catch (Exception e) {}
    }

    public void grantSuccessLog(String username) {
        try {
            String[] clientInfo = authorityFactory.get().extractClient(request);
            new Thread(() -> {
                RequestContextHolder.set(request, request.getHeader("user-agent"));
                try {
                    LoginLogDTO logDTO = LogUtil.buildLoginLog(applicationName);
                    logDTO.setType(LoginLogType.GRANT.getType().toString());
                    logDTO.setClientId(null == clientInfo ? null : clientInfo[0]);
                    logDTO.setUserName(username);
                    logFactory.getLoginLog().login(logDTO);
                } catch (Exception e) {}
            }).start();
        } catch (Exception e) {}
    }

    public void loginFailureLog(String username, String message) {
        try {
            String[] clientInfo = authorityFactory.get().extractClient(request);
            new Thread(() -> {
                RequestContextHolder.set(request, request.getHeader("user-agent"));
                try {
                    LoginLogDTO logDTO = LogUtil.buildLoginLog(applicationName);
                    logDTO.setType(LoginLogType.LOGIN.getType().toString());
                    logDTO.setClientId(null == clientInfo ? null : clientInfo[0]);
                    logDTO.setUserName(username);
                    logDTO.setStatus(StatusType.FAIL.getCode());
                    logDTO.setDesc(message);
                    logFactory.getLoginLog().login(logDTO);
                } catch (Exception e) {}
            }).start();
        } catch (Exception e) {}
    }

    public void logoutSuccessLog(String username) {
        try {
            String[] clientInfo = authorityFactory.get().extractClient(request);
            new Thread(() -> {
                try {
                    RequestContextHolder.set(request, request.getHeader("user-agent"));
                    LoginLogDTO logDTO = LogUtil.buildLoginLog(applicationName);
                    logDTO.setType(LoginLogType.LOGOUT.getType().toString());
                    logDTO.setClientId(null == clientInfo ? null : clientInfo[0]);
                    logDTO.setUserName(username);
                    logFactory.getLoginLog().logout(logDTO);
                } catch (Exception e) {}
            }).start();
        } catch (Exception e) {
        }
    }
}
